﻿/* ===============================================================================  
 * Copyright (c) 2010 Acid Frameworks
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * 
 * =============================================================================== */

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Acid.Math;

namespace Acid.Network 
{
    /// <summary>
    /// Acid Network Packet.
    /// </summary>
    [Serializable]
    public class Packet 
    {
        #region Types Enumeration

        public enum Types
        {
            Byte       = 1,
            Boolean    = 2,
            Integer    = 3,
            Float      = 4,
            Double     = 5,
            Vector2    = 6,
            Vector3    = 7,
            Vector4    = 8,
            Matrix     = 9,
            Quaternion = 10,
            Ray        = 11,
            Plane      = 12,
            String     = 13,
            ByteArray  = 14
        }

        #endregion

        #region Fields
        private string       id;
        private List<Types>  types;
        private List<object> values;
        #endregion

        #region Constructors

        public Packet() : this("unknown") { }

        public Packet(string id)
        {
            this.id     = id;
            this.types  = new List<Types> ();
            this.values = new List<object>();
        }

        #endregion

        #region Properties
        /// <summary>
        /// The unique id of this packet.
        /// </summary>
        public string       ID         
        {
            get { return this.id; }
            set { this.id = value; }
        }
        /// <summary>
        /// The values inside this packet.
        /// </summary>
        public List<object> ValueArray 
        {
            get { return this.values; }
        }
        /// <summary>
        /// The types inside this packet.
        /// </summary>
        public List<Types>  TypeArray  
        {
            get { return this.types; }
        }
        /// <summary>
        /// The amount of values stored in this packet.
        /// </summary>
        public int          Count      
        {
            get { return this.types.Count; }
        }
        /// <summary>
        /// The size in bytes of this packet.
        /// </summary>
        public int          PacketSize
        {
            get
            {
                byte[] data = Packet.Serialize(this);

                return data.Length;
            }
        }
        #endregion

        #region Setters
        public void       Add(byte       value)
        {
            this.types.Add(Types.Byte);
            this.values.Add(value);
        }
        public void       Add(bool       value)
        {
            this.types.Add(Types.Boolean);
            this.values.Add(value);
        }
        public void       Add(int        value)
        {
            this.types.Add(Types.Integer);
            this.values.Add(value);
        }
        public void       Add(float      value)
        {
            this.types.Add(Types.Float);
            this.values.Add(value);
        }
        public void       Add(double     value)
        {
            this.types.Add(Types.Double);
            this.values.Add(value);
        }
        public void       Add(Vector2    value)
        {
            this.types.Add(Types.Vector2);
            this.values.Add(value);
        }
        public void       Add(Vector3    value)
        {
            this.types.Add(Types.Vector3);
            this.values.Add(value);
        }
        public void       Add(Vector4    value)
        {
            this.types.Add(Types.Vector4);
            this.values.Add(value);
        }
        public void       Add(Matrix     value)
        {
            this.types.Add(Types.Matrix);
            this.values.Add(value);
        }
        public void       Add(Quaternion value)
        {
            this.types.Add(Types.Quaternion);
            this.values.Add(value);
        }
        public void       Add(Ray        value)
        {
            this.types.Add(Types.Ray);
            this.values.Add(value);
        }
        public void       Add(Plane      value)
        {
            this.types.Add (Types.Plane);
            this.values.Add(value);
        }
        public void       Add(string     value)
        {
            this.types.Add (Types.String);
            this.values.Add(value);
        }
        public void       Add(byte[]     value)
        {
            this.types.Add(Types.ByteArray);
            this.values.Add(value);
        }
        #endregion

        #region Getters
        public byte       GetByte       (int ordinal)
        {
            return (byte)values[ordinal];
        }
        public bool       GetBoolean    (int ordinal)
        {
            return (bool)this.values[ordinal];
        }
        public int        GetInteger    (int ordinal)
        {
            return (int)values[ordinal];
        }
        public float      GetFloat      (int ordinal)
        {
            return (float)values[ordinal];
        }
        public double     GetDouble     (int ordinal)
        {
            return (double)values[ordinal];
        }
        public Vector2    GetVector2    (int ordinal)
        {
            return (Vector2)values[ordinal];
        }
        public Vector3    GetVector3    (int ordinal)
        {
            return (Vector3)values[ordinal];
        }
        public Vector4    GetVector4    (int ordinal)
        {
            return (Vector4)values[ordinal];
        }
        public Matrix     GetMatrix     (int ordinal)
        {
            return (Matrix)values[ordinal];
        }
        public Quaternion GetQuaternion (int ordinal)
        {
            return (Quaternion)values[ordinal];
        }
        public Ray        GetRay        (int ordinal)
        {
            return (Ray)values[ordinal];
        }
        public Plane      GetPlane      (int ordinal)
        {
            return (Plane)this.values[ordinal];
        }
        public string     GetString     (int ordinal)
        {
            return (string)this.values[ordinal];
        }
        public byte    [] GetByteArray  (int ordinal)
        {
            return (byte[])this.values[ordinal];
        }
        #endregion

        #region Serialization

        /// <summary>
        /// Serializes this packet into transmitable bytes.
        /// </summary>
        /// <returns></returns>
        public static byte[] Serialize(Packet packet)
        {
            List<byte> data = new List<byte>();

            byte [] header = ASCIIEncoding.ASCII.GetBytes(packet.id);

            // Pack the header information. 

            data.AddRange(BitConverter.GetBytes(header.Length));

            data.AddRange(header);

            // Push the amount of types on the list.
            data.AddRange(BitConverter.GetBytes(packet.Count));

            // Push the Types on the list.
            for (int i = 0; i < packet.Count; i++)
            {
                data.Add((byte)packet.types[i]);
            }

            // Push the data on the list.
            for (int i = 0; i < packet.Count; i++)
            {
                switch (packet.types[i])
                {
                    case Types.Byte:
                        data.Add((byte)packet.values[i]);
                        break;
                    case Types.Boolean:
                        data.AddRange(BitConverter.GetBytes((bool)packet.values[i]));
                        break;
                    case Types.Integer:
                        data.AddRange(BitConverter.GetBytes((int)packet.values[i]));
                        break;
                    case Types.Float:
                        data.AddRange(BitConverter.GetBytes((float)packet.values[i]));
                        break;
                    case Types.Double:
                        data.AddRange(BitConverter.GetBytes((double)packet.values[i]));
                        break;
                    case Types.Vector2:
                        Vector2 v0 = (Vector2)packet.values[i];
                        data.AddRange(BitConverter.GetBytes(v0.X));
                        data.AddRange(BitConverter.GetBytes(v0.Y));
                        break;
                    case Types.Vector3:
                        Vector3 v1 = (Vector3)packet.values[i];
                        data.AddRange(BitConverter.GetBytes(v1.X));
                        data.AddRange(BitConverter.GetBytes(v1.Y));
                        data.AddRange(BitConverter.GetBytes(v1.Z));
                        break;
                    case Types.Vector4:
                        Vector4 v2 = (Vector4)packet.values[i];
                        data.AddRange(BitConverter.GetBytes(v2.X));
                        data.AddRange(BitConverter.GetBytes(v2.Y));
                        data.AddRange(BitConverter.GetBytes(v2.Z));
                        data.AddRange(BitConverter.GetBytes(v2.W));
                        break;
                    case Types.Matrix:
                        Matrix m = (Matrix)packet.values[i];
                        data.AddRange(BitConverter.GetBytes(m.M11));
                        data.AddRange(BitConverter.GetBytes(m.M12));
                        data.AddRange(BitConverter.GetBytes(m.M13));
                        data.AddRange(BitConverter.GetBytes(m.M14));
                        data.AddRange(BitConverter.GetBytes(m.M21));
                        data.AddRange(BitConverter.GetBytes(m.M22));
                        data.AddRange(BitConverter.GetBytes(m.M23));
                        data.AddRange(BitConverter.GetBytes(m.M24));
                        data.AddRange(BitConverter.GetBytes(m.M31));
                        data.AddRange(BitConverter.GetBytes(m.M32));
                        data.AddRange(BitConverter.GetBytes(m.M33));
                        data.AddRange(BitConverter.GetBytes(m.M34));
                        data.AddRange(BitConverter.GetBytes(m.M41));
                        data.AddRange(BitConverter.GetBytes(m.M42));
                        data.AddRange(BitConverter.GetBytes(m.M43));
                        data.AddRange(BitConverter.GetBytes(m.M44));
                        break;
                    case Types.Quaternion:
                        Quaternion q = (Quaternion)packet.values[i];
                        data.AddRange(BitConverter.GetBytes(q.X));
                        data.AddRange(BitConverter.GetBytes(q.Y));
                        data.AddRange(BitConverter.GetBytes(q.Z));
                        data.AddRange(BitConverter.GetBytes(q.W));
                        break;
                    case Types.Ray:
                        Ray ray = (Ray)packet.values[i];
                        data.AddRange(BitConverter.GetBytes(ray.Position.X));
                        data.AddRange(BitConverter.GetBytes(ray.Position.Y));
                        data.AddRange(BitConverter.GetBytes(ray.Position.Z));
                        data.AddRange(BitConverter.GetBytes(ray.Direction.X));
                        data.AddRange(BitConverter.GetBytes(ray.Direction.Y));
                        data.AddRange(BitConverter.GetBytes(ray.Direction.Z));
                        break;
                    case Types.Plane:
                        Plane plane = (Plane)packet.values[i];
                        data.AddRange(BitConverter.GetBytes(plane.Normal.X));
                        data.AddRange(BitConverter.GetBytes(plane.Normal.Y));
                        data.AddRange(BitConverter.GetBytes(plane.Normal.Z));
                        data.AddRange(BitConverter.GetBytes(plane.D));
                        break;
                    case Types.String:
                        string s = (string)packet.values[i];
                        data.AddRange(BitConverter.GetBytes(s.Length));
                        data.AddRange(ASCIIEncoding.ASCII.GetBytes(s));
                        break;
                    case Types.ByteArray:
                        byte [] byteArray = (byte[])packet.values[i];
                        data.AddRange(BitConverter.GetBytes(byteArray.Length));
                        data.AddRange(byteArray);
                        break;
                    default:
                        break;
                }
            }
            return data.ToArray();
        }
        
        /// <summary>
        /// Deserializes this packet.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static Packet Deserialize(byte[] data)
        {
            Packet packet = new Packet();

            int length = BitConverter.ToInt32(data, 0);

            List<byte> temp = new List<byte>(BitConverter.GetBytes(length));

            temp.Reverse();

          

           Console.WriteLine(BitConverter.ToInt32(temp.ToArray(), 0));

            packet.id = ASCIIEncoding.ASCII.GetString(data, 4, length);

            

            int typeCount = BitConverter.ToInt32(data, 4 + length);

            for (int i = 0; i < typeCount; i++)
            {
                packet.types.Add((Types)data[4 + length + 4 + i]);
            }

            int cursor = (4 + length + 4) + typeCount;

            foreach (Types type in packet.types)
            {
                switch (type)
                {
                    case Types.Byte:
                        packet.values.Add(data[cursor]);
                        cursor += 1;
                        break;
                    case Types.Boolean:
                        packet.values.Add(BitConverter.ToBoolean(data, cursor));
                        cursor += 1;
                        break;
                    case Types.Integer:
                        packet.values.Add(BitConverter.ToInt32(data, cursor));
                        cursor += 4;
                        break;
                    case Types.Float:
                        packet.values.Add(BitConverter.ToSingle(data, cursor));
                        cursor += 4;
                        break;
                    case Types.Double:
                        packet.values.Add(BitConverter.ToDouble(data, cursor));
                        cursor += 8;
                        break;
                    case Types.Vector2:
                        packet.values.Add(new Vector2(BitConverter.ToSingle(data, cursor),
                                                      BitConverter.ToSingle(data, cursor + 4)));
                        cursor += 8;
                        break;
                    case Types.Vector3:
                        packet.values.Add(new Vector3(BitConverter.ToSingle(data, cursor),
                                                      BitConverter.ToSingle(data, cursor + 4),
                                                      BitConverter.ToSingle(data, cursor + 8)));
                        cursor += 12;
                        break;
                    case Types.Vector4:
                        packet.values.Add(new Vector4(BitConverter.ToSingle(data, cursor),
                                                      BitConverter.ToSingle(data, cursor + 4),
                                                      BitConverter.ToSingle(data, cursor + 8),
                                                      BitConverter.ToSingle(data, cursor + 12)));
                        cursor += 16;
                        break;
                    case Types.Matrix:
                        packet.values.Add(new Matrix(BitConverter.ToSingle(data, cursor),
                                                     BitConverter.ToSingle(data, cursor + 4),
                                                     BitConverter.ToSingle(data, cursor + 8),
                                                     BitConverter.ToSingle(data, cursor + 12),
                                                     BitConverter.ToSingle(data, cursor + 16),
                                                     BitConverter.ToSingle(data, cursor + 20),
                                                     BitConverter.ToSingle(data, cursor + 24),
                                                     BitConverter.ToSingle(data, cursor + 28),
                                                     BitConverter.ToSingle(data, cursor + 32),
                                                     BitConverter.ToSingle(data, cursor + 36),
                                                     BitConverter.ToSingle(data, cursor + 40),
                                                     BitConverter.ToSingle(data, cursor + 44),
                                                     BitConverter.ToSingle(data, cursor + 48),
                                                     BitConverter.ToSingle(data, cursor + 52),
                                                     BitConverter.ToSingle(data, cursor + 56),
                                                     BitConverter.ToSingle(data, cursor + 60)));
                        cursor += 64;
                        break;
                    case Types.Quaternion:
                        packet.values.Add(new Quaternion(BitConverter.ToSingle(data, cursor),
                                                         BitConverter.ToSingle(data, cursor + 4),
                                                         BitConverter.ToSingle(data, cursor + 8),
                                                         BitConverter.ToSingle(data, cursor + 12)));
                        cursor += 16;
                        break;
                    case Types.Ray:
                        packet.values.Add(new Ray(new Vector3(BitConverter.ToSingle(data, cursor),
                                                              BitConverter.ToSingle(data, cursor + 4),
                                                              BitConverter.ToSingle(data, cursor + 8)),
                                                  new Vector3(BitConverter.ToSingle(data, cursor + 12),
                                                              BitConverter.ToSingle(data, cursor + 16),
                                                              BitConverter.ToSingle(data, cursor + 20))));
                        cursor += 24;
                        break;
                    case Types.Plane:
                        packet.values.Add(new Plane(BitConverter.ToSingle(data, cursor),
                                                    BitConverter.ToSingle(data, cursor + 4),
                                                    BitConverter.ToSingle(data, cursor + 8),
                                                    BitConverter.ToSingle(data, cursor + 12)));
                        cursor += 16;
                        break;
                    case Types.String:
                        int slength = BitConverter.ToInt32(data, cursor);
                        packet.values.Add(ASCIIEncoding.ASCII.GetString(data, cursor + 4, slength));
                        cursor += 4 + slength;
                        break;
                    case Types.ByteArray:
                        int dlength = BitConverter.ToInt32(data, cursor);
                        byte[] ddata = new byte[dlength];
                        Array.Copy(data, cursor + 4, ddata, 0, dlength);
                        packet.values.Add(ddata);
                        cursor += 4 + dlength;
                        break;
                    default:
                        break;
                }
            }
            return packet;
           
        }

        #endregion

        #region Overrides

        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();

            using (StringWriter writer = new StringWriter(builder))
            {
                writer.WriteLine    ("Packet ID   : {0}", this.id);
                
                writer.WriteLine    ("Packet Size : {0} bytes", this.PacketSize);
                
                for (int i = 0; i < this.types.Count; i++)
                {
                    if (this.types[i] == Types.String)
                    {
                        byte [] data = ASCIIEncoding.ASCII.GetBytes((string)this.values[i]);

                        writer.WriteLine("Type {0} -> {1} [{2}]", i, this.types[i].ToString(), data.Length);
                    }
                    else
                    {
                        writer.WriteLine("Type {0} -> {1}", i, this.types[i].ToString());
                    }
                }
            }

            return builder.ToString();
        }

        #endregion
    }
}
